#include "nn_utils.h"
#include "clipper.hpp"
#ifdef LYNXI_PLUGIN
#include "lyn_plugin_dev.h"
#endif

namespace ocr {
bool cvPointCompare(const cv::Point &a, const cv::Point &b) {
  return a.x < b.x;
}

std::vector<cv::Point> getMinBoxes(const std::vector<cv::Point> &inVec,
                                   float &minSideLen, float &allEdgeSize) {
  std::vector<cv::Point> minBoxVec;
  cv::RotatedRect textRect = cv::minAreaRect(inVec);
  cv::Mat boxPoints2f;
  cv::boxPoints(textRect, boxPoints2f);

  float *p1 = (float *)boxPoints2f.data;
  std::vector<cv::Point> tmpVec;
  for (int i = 0; i < 4; ++i, p1 += 2) {
    tmpVec.emplace_back(int(p1[0]), int(p1[1]));
  }

  std::sort(tmpVec.begin(), tmpVec.end(), cvPointCompare);

  minBoxVec.clear();

  int index1, index2, index3, index4;
  if (tmpVec[1].y > tmpVec[0].y) {
    index1 = 0;
    index4 = 1;
  } else {
    index1 = 1;
    index4 = 0;
  }

  if (tmpVec[3].y > tmpVec[2].y) {
    index2 = 2;
    index3 = 3;
  } else {
    index2 = 3;
    index3 = 2;
  }

  minBoxVec.clear();

  minBoxVec.push_back(tmpVec[index1]);
  minBoxVec.push_back(tmpVec[index2]);
  minBoxVec.push_back(tmpVec[index3]);
  minBoxVec.push_back(tmpVec[index4]);

  minSideLen = (std::min)(textRect.size.width, textRect.size.height);
  allEdgeSize = 2.f * (textRect.size.width + textRect.size.height);

  return minBoxVec;
}

float boxScoreFast(const cv::Mat &inMat, const std::vector<cv::Point> &inBox) {
  std::vector<cv::Point> box = inBox;
  int width = inMat.cols;
  int height = inMat.rows;
  int maxX = -1, minX = 1000000, maxY = -1, minY = 1000000;
  for (size_t i = 0; i < box.size(); ++i) {
    if (maxX < box[i].x)
      maxX = box[i].x;
    if (minX > box[i].x)
      minX = box[i].x;
    if (maxY < box[i].y)
      maxY = box[i].y;
    if (minY > box[i].y)
      minY = box[i].y;
  }
  maxX = (std::min)((std::max)(maxX, 0), width - 1);
  minX = (std::max)((std::min)(minX, width - 1), 0);
  maxY = (std::min)((std::max)(maxY, 0), height - 1);
  minY = (std::max)((std::min)(minY, height - 1), 0);

  for (size_t i = 0; i < box.size(); ++i) {
    box[i].x = box[i].x - minX;
    box[i].y = box[i].y - minY;
  }

  std::vector<std::vector<cv::Point>> maskBox;
  maskBox.push_back(box);
  cv::Mat maskMat(maxY - minY + 1, maxX - minX + 1, CV_8UC1,
                  cv::Scalar(0, 0, 0));
  cv::fillPoly(maskMat, maskBox, cv::Scalar(1, 1, 1), 1);
  return cv::mean(inMat(cv::Rect(cv::Point(minX, minY),
                                 cv::Point(maxX + 1, maxY + 1)))
                      .clone(),
                  maskMat)
      .val[0];
}

std::vector<cv::Point> unClip(const std::vector<cv::Point> &inBox,
                              float perimeter, float unClipRatio) {
  std::vector<cv::Point> outBox;
  ClipperLib::Path poly;

  for (size_t i = 0; i < inBox.size(); ++i) {
    poly.push_back(ClipperLib::IntPoint(inBox[i].x, inBox[i].y));
  }

  double distance = unClipRatio * ClipperLib::Area(poly) / (double)perimeter;

  ClipperLib::ClipperOffset clipperOffset;
  clipperOffset.AddPath(poly, ClipperLib::JoinType::jtRound,
                        ClipperLib::EndType::etClosedPolygon);
  ClipperLib::Paths polys;
  polys.push_back(poly);
  clipperOffset.Execute(polys, distance);

  outBox.clear();
  std::vector<cv::Point> rsVec;
  for (size_t i = 0; i < polys.size(); ++i) {
    ClipperLib::Path tmpPoly = polys[i];
    for (size_t j = 0; j < tmpPoly.size(); ++j) {
      outBox.emplace_back(tmpPoly[j].X, tmpPoly[j].Y);
    }
  }
  return outBox;
}

std::vector<TextBox> findRsBoxes(const cv::Mat &fMapMat,
                                 const cv::Mat &norfMapMat,
                                 const float boxScoreThresh,
                                 const float unClipRatio) {
  float minArea = 3;
  std::vector<TextBox> rsBoxes;
  rsBoxes.clear();
  std::vector<std::vector<cv::Point>> contours;
  cv::findContours(norfMapMat, contours, cv::RETR_LIST,
                   cv::CHAIN_APPROX_SIMPLE);
  for (size_t i = 0; i < contours.size(); ++i) {
    float minSideLen, perimeter;
    std::vector<cv::Point> minBox =
        getMinBoxes(contours[i], minSideLen, perimeter);
    if (minSideLen < minArea)
      continue;
    float score = boxScoreFast(fMapMat, contours[i]);
    if (score < boxScoreThresh)
      continue;
    //---use clipper start---
    std::vector<cv::Point> clipBox = unClip(minBox, perimeter, unClipRatio);
    std::vector<cv::Point> clipMinBox =
        getMinBoxes(clipBox, minSideLen, perimeter);
    //---use clipper end---

    if (minSideLen < minArea + 2)
      continue;

    for (size_t j = 0; j < clipMinBox.size(); ++j) {
      clipMinBox[j].x = (clipMinBox[j].x / 1.0);
      clipMinBox[j].x =
          (std::min)((std::max)(clipMinBox[j].x, 0), norfMapMat.cols);

      clipMinBox[j].y = (clipMinBox[j].y / 1.0);
      clipMinBox[j].y =
          (std::min)((std::max)(clipMinBox[j].y, 0), norfMapMat.rows);
    }

    rsBoxes.emplace_back(TextBox{clipMinBox, score});
  }
  reverse(rsBoxes.begin(), rsBoxes.end());

  return rsBoxes;
}

std::vector<TextBox> getTextBoxes(const int width, const int height,
                                  const int modelWidth, const int modelHeight,
                                  cv::Mat fMapMat, float boxScoreThresh,
                                  float boxThresh, float unClipRatio) {

  int m_iResizeWidth = 0;
  int m_iResizeHeight = 0;
  // 图片填充参数
  int m_iPadX = 0;
  int m_iPadY = 0;
  float scale = 0.f;
  if (width > height) {
    m_iResizeWidth = modelWidth;
    scale = (float)modelWidth / width;
    // & ~1 是为了取偶数，yuv格式不能用奇数去进行ipe
    m_iResizeHeight = (height * modelWidth / width) & ~1;
    m_iPadX = 0;
    m_iPadY = (modelHeight - m_iResizeHeight) / 2; //
  } else {
    scale = (float)modelHeight / height;
    m_iResizeWidth = (width * modelHeight / height) & ~1;
    m_iResizeHeight = modelHeight;
    m_iPadX = (modelWidth - m_iResizeWidth) / 2;
    m_iPadY = 0;
  }

  // cv::Mat fMapMat(modelHeight, modelWidth, CV_32FC1, outData);
  cv::Mat norfMapMat;
  norfMapMat = fMapMat > boxThresh;

  cv::dilate(norfMapMat, norfMapMat, cv::Mat(), cv::Point(-1, -1), 1);

  std::vector<TextBox> result =
      findRsBoxes(fMapMat, norfMapMat, boxScoreThresh, unClipRatio);
  for (size_t i = 0; i < result.size(); i++) {
    for (size_t j = 0; j < result[i].boxPoint.size(); j++) {
      float x = (result[i].boxPoint[j].x - m_iPadX) / scale;
      float y = (result[i].boxPoint[j].y - m_iPadY) / scale;
      x = std::max(std::min(x, (float)(width - 1)), 0.f);
      y = std::max(std::min(y, (float)(height - 1)), 0.f);
      result[i].boxPoint[j].x = x;
      result[i].boxPoint[j].y = y;
    }
  }

  return result;
}

int16_t expBitNum = 5;
int16_t baseBitNum = 15 - expBitNum;
int maxExp = 32;    // pow(2, expBitNum);
int maxBase = 1024; // pow(2, baseBitNum);
int biasExp = 15;   // maxExp / 2 - 1;
int sig[2] = {1, -1};
float result = 5.96046e-08;

float half2float(int16_t ib) {
  int16_t s, e, m;
  s = (ib >> 15) & 0x1;
  e = (ib >> 10) & 0x1f;
  m = ib & 0x3ff;

  // added by puyang.wang@lynxi.com
  {
    if (0 == e)
      return sig[s] * m * result;
    else {
      union {
        unsigned int u32;
        float f32;
      } ou;

      e = (0x1f == e) ? 0xff : (e - 15 + 127);
      ou.u32 = (s << 31) | (e << 23) | (m << 13);
      return ou.f32;
    }
  }
}

std::vector<TextBox> det_postprocess(void *data, size_t img_w, size_t img_h,
                                     size_t model_w, size_t model_h) {
  int16_t *fp16_data_ptr = (int16_t *)data;
  auto outpred = cv::Mat(model_h, model_w, CV_32FC1);
  float *fp32_data_ptr = (float *)outpred.data;
  for (size_t i = 0; i < model_w * model_h; i++) {
    fp32_data_ptr[i] = half2float(fp16_data_ptr[i]);
  }
  return getTextBoxes(img_w, img_h, model_w, model_h, outpred, 0.7, 0.3, 1.5f);
}

bool compareBoxWidth(const TextBox &a, const TextBox &b) {
  return abs(a.boxPoint[0].x - a.boxPoint[1].x) >
         abs(b.boxPoint[0].x - b.boxPoint[1].x);
}

cv::Mat getRotateCropImage(const cv::Mat &src, std::vector<cv::Point> box) {
  cv::Mat image;
  src.copyTo(image);
  std::vector<cv::Point> points = box;

  int collectX[4] = {box[0].x, box[1].x, box[2].x, box[3].x};
  int collectY[4] = {box[0].y, box[1].y, box[2].y, box[3].y};
  int left = int(*std::min_element(collectX, collectX + 4));
  int right = int(*std::max_element(collectX, collectX + 4));
  int top = int(*std::min_element(collectY, collectY + 4));
  int bottom = int(*std::max_element(collectY, collectY + 4));

  cv::Mat imgCrop;
  image(cv::Rect(left, top, right - left, bottom - top)).copyTo(imgCrop);

  for (size_t i = 0; i < points.size(); i++) {
    points[i].x -= left;
    points[i].y -= top;
  }

  int imgCropWidth = int(sqrt(pow(points[0].x - points[1].x, 2) +
                              pow(points[0].y - points[1].y, 2)));
  int imgCropHeight = int(sqrt(pow(points[0].x - points[3].x, 2) +
                               pow(points[0].y - points[3].y, 2)));

  cv::Point2f ptsDst[4];
  ptsDst[0] = cv::Point2f(0., 0.);
  ptsDst[1] = cv::Point2f(imgCropWidth, 0.);
  ptsDst[2] = cv::Point2f(imgCropWidth, imgCropHeight);
  ptsDst[3] = cv::Point2f(0.f, imgCropHeight);

  cv::Point2f ptsSrc[4];
  ptsSrc[0] = cv::Point2f(points[0].x, points[0].y);
  ptsSrc[1] = cv::Point2f(points[1].x, points[1].y);
  ptsSrc[2] = cv::Point2f(points[2].x, points[2].y);
  ptsSrc[3] = cv::Point2f(points[3].x, points[3].y);

  cv::Mat M = cv::getPerspectiveTransform(ptsSrc, ptsDst);

  cv::Mat partImg;
  cv::warpPerspective(imgCrop, partImg, M,
                      cv::Size(imgCropWidth, imgCropHeight),
                      cv::BORDER_REPLICATE);

  if (float(partImg.rows) >= float(partImg.cols) * 1.5) {
    cv::Mat srcCopy = cv::Mat(partImg.rows, partImg.cols, partImg.depth());
    cv::transpose(partImg, srcCopy);
    cv::flip(srcCopy, srcCopy, 0);
    return srcCopy;
  } else {
    return partImg;
  }
}

std::vector<cv::Mat> getPartImages(const cv::Mat &src,
                                   std::vector<TextBox> &textBoxes) {
  std::sort(textBoxes.begin(), textBoxes.end(), compareBoxWidth);
  std::vector<cv::Mat> partImages;
  if (textBoxes.size() > 0) {
    for (size_t i = 0; i < textBoxes.size(); ++i) {
      cv::Mat partImg = getRotateCropImage(src, textBoxes[i].boxPoint);
      partImages.emplace_back(partImg);
    }
  }

  return partImages;
}

// 同步调用接口，先保证功能正常
std::vector<cv::Mat> crop(void *frame, size_t w, size_t h,
                          std::vector<TextBox> &reslut) {
  // 如果std::vector<TextBox>.size小于等于batch size，则一次推理完
  // 否则则需要推理多次
  auto numDets = reslut.size();
  if (numDets == 0) { // 检测对象为空时，直接返回，即提取特征为空
    return {};
  }

  // 暂时使用opencv来进行图片预处理，因为目前ipe并不支持cv::warpPerspective
  cv::Mat hostImageYUV(h * 3 / 2, w, CV_8UC1, frame); // YUV
  cv::Mat hostImage(h, w, CV_8UC3);                   // YUV
  // 拷贝YUV的原图数据到host mat
  // YUV2RGB
  cv::cvtColor(hostImageYUV, hostImage, cv::COLOR_YUV2BGR_NV12);
  return getPartImages(hostImage, reslut);
}

template <class ForwardIterator>
inline static size_t argmax(ForwardIterator first, ForwardIterator last) {
  return std::distance(first, std::max_element(first, last));
}

// void scoreToTextLine(const int16_t *outputData, TextBox &res,
//                      std::vector<std::string> mKeys, size_t model_h,
//                      size_t model_w) {
//   std::string strRes;
//   std::vector<float> scores;
//   int lastIndex = 0;
//   int maxIndex;
//   int16_t maxValue;
//   std::vector<int> maxIndexVec;

//   for (size_t i = 0; i < model_h; i++) {
//     maxIndex = 0;
//     maxValue = -10000;

//     maxIndex =
//         int(argmax(outputData + i * model_w, outputData + (i + 1) *
//         model_w));
//     maxValue = outputData[i * model_w + maxIndex];

//     if (maxIndex > 0 && maxIndex < (int)mKeys.size() &&
//         (!(i > 0 && maxIndex == lastIndex))) {
//       maxIndexVec.emplace_back(maxIndex);
//       scores.emplace_back(half2float(maxValue));
//       strRes.append(mKeys[maxIndex - 1]);
//     }
//     lastIndex = maxIndex;
//   }

//   res.textLine.text = strRes;
//   res.textLine.charScores = scores;

//   return;
// }

void scoreToTextLine(const int16_t *outputData, TextBox &res, mKeys *mKeys,
                     size_t model_h, size_t model_w) {
  std::string strRes;
  std::vector<float> scores;
  int lastIndex = 0;
  int maxIndex;
  int16_t maxValue;
  std::vector<int> maxIndexVec;

  for (size_t i = 0; i < model_h; i++) {
    maxIndex = 0;
    maxValue = -10000;

    maxIndex =
        int(argmax(outputData + i * model_w, outputData + (i + 1) * model_w));
    maxValue = outputData[i * model_w + maxIndex];

    if (maxIndex > 0 && maxIndex < mKeys->num &&
        (!(i > 0 && maxIndex == lastIndex))) {
      maxIndexVec.emplace_back(maxIndex);
      scores.emplace_back(half2float(maxValue));
      strRes.append(mKeys->data[maxIndex - 1].data);
    }
    lastIndex = maxIndex;
  }

  res.textLine.text = strRes;
  res.textLine.charScores = scores;

  return;
}

} // namespace ocr
